跳到主要内容

Ad-hoc 命令与常用模块

Ad-hoc 是 Ansible 的「一次性命令」模式:不写 playbook,直接一条命令对一批机器做一件事,适合巡检、临时改动、验证想法。这篇笔记记录 ad-hoc 的命令格式、提权方式、幂等性注意事项,以及日常运维里使用频率最高的模块用法。示例环境:控制节点 macOS / Linux,受管节点 Ubuntu 22.04,命令均可直接照抄执行(把 inventory 换成自己的清单即可)。

Ad-hoc 命令格式​

基本语法:

ansible <pattern> -m <module> -a "模块参数" [-i inventory] [-b]
  • <pattern>:目标主机或组,如 all、web、web:&prod(详见上一篇 Patterns 一节)。
  • -m:模块名,用 FQCN 全名,如 ansible.builtin.ping。省略 -m 时默认用 ansible.builtin.command。
  • -a:模块参数,key=value 形式用空格分隔。
  • -i:指定 inventory 文件;ansible.cfg 里配置过就可以省略。

先跑一条最经典的验证命令:

ansible web -i inventory/hosts.ini -m ansible.builtin.ping
web1.example.com | SUCCESS => {
"changed": false,
"ping": "pong"
}
什么时候用 ad-hoc,什么时候写 playbook
  • 查状态、验证连通性、临时重启服务、批量看一眼磁盘:ad-hoc,一条命令的事。
  • 步骤超过两条、需要重复执行、要留档评审:写 playbook。经验法则是「第二次还要跑,就该落进 playbook」,ad-hoc 命令执行完就消失了,不留痕迹。

提权​

装软件、改系统配置需要 root,用 -b(--become)提权:

ansible web -b -m ansible.builtin.command -a "cat /etc/shadow"
参数说明
-b, --become执行时提权,默认用 sudo 切到 root
--become-user=用户名提到指定用户,默认 root
-K, --ask-become-pass交互式询问 sudo 密码(受管节点 sudo 需要密码时必加)

也可以在 inventory 里固定下来,不用每次加参数:

[web]
web1.example.com ansible_user=ubuntu

[web:vars]
ansible_become=true
ansible_become_method=sudo
ansible_become_user=root
生产环境建议 NOPASSWD

受管节点的 sudo 配置里给运维账号开 NOPASSWD,免掉 -K 交互,CI 里也能跑。密码不要写进 ansible_become_password 明文变量,真要走密码就交给 Ansible Vault 加密。

幂等性​

幂等是指重复执行同一操作,结果与执行一次相同。Ansible 的大多数模块是幂等的:ansible.builtin.package 装已经装好的包不会重装,输出里 changed=false,可以放心反复跑。

但 command、shell、raw 这三个「裸执行」模块不幂等:Ansible 不知道你的命令干了什么,每次都原样执行、每次都报 changed=true。用 creates / removes 参数补救——指定文件存在(或不存在)时跳过执行:

# /opt/app 已存在就跳过解压,重复执行安全
ansible web -b -m ansible.builtin.command -a "tar -xf /tmp/app.tar.gz -C /opt creates=/opt/app"
能用专用模块就别裸执行

creates 只是补救措施。能用 ansible.builtin.copy 下发文件就别 scp,能用 ansible.builtin.package 装包就别 apt-get install——专用模块幂等、有状态报告,playbook 化时也直接可用。

高频模块笔记​

下面按使用频率记录常用模块,每个给用途、可跑示例和注意事项。

ansible.builtin.ping​

用途:验证受管节点的连通性(SSH 可达 + Python 可用),不是 ICMP ping。

ansible all -m ansible.builtin.ping

注意事项:返回 pong 才说明 Ansible 真的能管理这台机器;SSH 能连但 Python 不可用时 ping 也会失败。

ansible.builtin.setup​

用途:收集受管节点的 facts(系统信息:发行版、内存、IP、挂载点等),排障和写变量模板时常用。

# 只看发行版和内核
ansible web -m ansible.builtin.setup -a "filter=ansible_distribution*"

# 看默认网卡 IP
ansible web -m ansible.builtin.setup -a "filter=ansible_default_ipv4"

注意事项:filter 支持通配符;不带 filter 全量输出很长,建议接 | less 或先想清楚要查什么字段。

ansible.builtin.command / shell / raw​

用途:裸执行命令的三个兄弟,能力递增、安全性递减。

# command:不走 shell,最安全,省略 -m 时默认就是它
ansible web -m ansible.builtin.command -a "uptime"

# shell:走 /bin/sh,支持管道、重定向、通配符
ansible web -m ansible.builtin.shell -a "df -h | grep '/$'"

# raw:连 Python 都不用,直接 SSH 执行,用于给裸机装 Python
ansible web -u root -m ansible.builtin.raw -a "apt-get update && apt-get install -y python3"

选择原则:

模块管道/重定向风险典型场景
command不支持低,无 shell 解析默认选择,执行单条命令
shell支持有注入风险,参数拼接要小心必须用管道/重定向时
raw支持最高,完全裸奔受管节点没有 Python 的初始化引导

ansible.builtin.copy​

用途:把文件从控制节点推到受管节点,或直接写入一段文本内容。

# 写一小段内容(适合临时塞个配置片段)
ansible web -b -m ansible.builtin.copy -a "content='server_tokens off;' dest=/etc/nginx/conf.d/hardening.conf backup=yes"

# 推本地文件并设权限
ansible web -b -m ansible.builtin.copy -a "src=./nginx.conf dest=/etc/nginx/nginx.conf mode=0644 owner=root backup=yes"

注意事项:content 和 src 二选一;backup=yes 会在覆盖前备份原文件,改系统配置时建议带上;mode 写成 '0644' 字符串形式最稳妥。另外 ad-hoc 模式下 content 里的 \n 会按字面写进文件,多行内容请交给 playbook。

ansible.builtin.template​

用途:把控制节点上的 Jinja2 模板(.j2)渲染后推到受管节点,模板里可以用 inventory 变量和 facts。

ansible web -b -m ansible.builtin.template -a "src=./templates/nginx.conf.j2 dest=/etc/nginx/nginx.conf mode=0644"

注意事项:ad-hoc 模式下模板变量只能来自 inventory / facts,想传参得加 -e;模板逻辑一旦变复杂就该写 playbook 了。

ansible.builtin.fetch​

用途:与 copy 相反,把文件从受管节点拉回控制节点,收日志、收配置文件用。

ansible web -m ansible.builtin.fetch -a "src=/var/log/nginx/access.log dest=./logs/ flat=yes"

注意事项:默认会按 dest/主机名/src完整路径 建目录层级,加 flat=yes 直接平铺到 dest 下;不能拉目录。

ansible.builtin.file​

用途:管文件属性——建目录、建软链、改权限、删文件。

# 建目录
ansible web -b -m ansible.builtin.file -a "path=/opt/app/logs state=directory mode=0755"

# 建软链
ansible web -b -m ansible.builtin.file -a "src=/opt/app/current dest=/srv/app state=link"

# 删文件
ansible web -b -m ansible.builtin.file -a "path=/tmp/old_dump.sql state=absent"

注意事项:state 取值 directory / file / link / touch / absent;删错路径不可恢复,absent 操作先用 --check 跑一遍。

ansible.builtin.package 与 ansible.builtin.apt​

用途:package 是跨发行版通用包管理入口(自动落到 apt/dnf),apt 是 Debian/Ubuntu 专用、参数更全。

# 通用写法,一份命令打多种发行版
ansible web -b -m ansible.builtin.package -a "name=curl state=present"

# Ubuntu 专用:先更新索引再装,索引 1 小时内有效
ansible web -b -m ansible.builtin.apt -a "name=nginx state=present update_cache=yes cache_valid_time=3600"

注意事项:新装的 Ubuntu 机器 apt 索引可能是旧的甚至空的,直接装包会报「包不存在」,加 update_cache=yes 最省心;state=latest 会升级到最新版,生产环境用 present 锁稳。

ansible.builtin.service 与 systemd​

用途:管理服务状态。service 自动识别 init 系统(Ubuntu 22.04 上是 systemd);ansible.builtin.systemd_service(旧名 ansible.builtin.systemd 仍可用)提供 daemon_reload 等 systemd 专有参数。

# 启动并设置开机自启
ansible web -b -m ansible.builtin.service -a "name=nginx state=started enabled=yes"

# 改了 unit 文件后重载 systemd 再重启
ansible web -b -m ansible.builtin.systemd_service -a "name=app state=restarted daemon_reload=yes"

注意事项:state(started/stopped/restarted/reloaded)管现在跑不跑,enabled(yes/no)管开机启不启动,两件事互不影响,要就一起写。

ansible.builtin.user / ansible.builtin.group​

用途:管理账号和组。

ansible web -b -m ansible.builtin.group -a "name=deploy gid=2000"
ansible web -b -m ansible.builtin.user -a "name=deploy group=deploy shell=/bin/bash create_home=yes"

注意事项:加 sudo 组写 groups=sudo append=yes,不带 append=yes 会清掉用户原有的附加组,这是高频事故点。

ansible.builtin.git​

用途:在受管节点上 clone / 更新 git 仓库,简单的代码下发方式。

ansible web -b -m ansible.builtin.git -a "repo=https://github.com/example/app.git dest=/opt/app version=main"

注意事项:私有仓库走 SSH 地址并在受管节点配好 deploy key;version 可以是分支、tag 或 commit 哈希,重复执行会幂等地保持在该版本。

ansible.builtin.get_url 与 ansible.builtin.uri​

用途:get_url 在受管节点上下载文件到本地磁盘;uri 用来调 HTTP API(健康检查、触发 webhook)。

# 下载二进制到受管节点
ansible web -b -m ansible.builtin.get_url -a "url=https://example.com/app-v1.2.3.tar.gz dest=/opt/app.tar.gz mode=0644"

# 调健康检查接口,非 200 视为失败
ansible web -m ansible.builtin.uri -a "url=http://127.0.0.1:8080/health status_code=200"

注意事项:uri 带 JSON body、请求头时 -a 的引号嵌套很折磨人,这类调用建议直接写进 playbook 用 YAML 表达。

ansible.builtin.cron​

用途:管理 crontab 条目,比手改 /etc/crontab 或 crontab -e 干净。

ansible web -b -m ansible.builtin.cron -a "name='disk cleanup' hour=3 minute=0 job='/usr/local/bin/cleanup.sh >> /var/log/cleanup.log 2>&1'"

注意事项:name 是条目的唯一标识,改时间或命令时靠它定位;删条目用 state=absent 加同样的 name。

ansible.posix.synchronize​

用途:基于 rsync 的增量同步,推一整个目录(比如前端构建产物 dist/)比 copy 快得多。

ansible web -b -m ansible.posix.synchronize -a "src=./dist/ dest=/var/www/html/ mode=push delete=yes"

注意事项:控制节点和受管节点两端都要装 rsync;ansible.posix collection 不在 ansible-core 里,单独用 ansible-core 的话先 ansible-galaxy collection install ansible.posix;delete=yes 会让目标目录与源完全一致,小心删到不该删的。

ansible.builtin.debug​

用途:打印变量或消息,调试 inventory 变量、facts 时的第一选择。

# 看某台机器最终拿到的变量值
ansible web1.example.com -m ansible.builtin.debug -a "var=nginx_port"

# 看 facts 里的发行版
ansible web -m ansible.builtin.debug -a "msg='系统是 {{ ansible_distribution }} {{ ansible_distribution_version }}'"

注意事项:var= 直接给变量名(不带大括号),msg= 里是模板字符串(带大括号),两者别写反。

一个完整小例子​

用三条 ad-hoc 给 web 组全新装好 nginx 并启动,体会一下「装包 → 下发配置 → 起服务」的最小闭环:

# 1. 装 nginx(先更新 apt 索引)
ansible web -b -m ansible.builtin.apt -a "name=nginx state=present update_cache=yes cache_valid_time=3600"

# 2. 下发一个首页
ansible web -b -m ansible.builtin.copy -a "content='<h1>It works</h1>' dest=/var/www/html/index.html mode=0644"

# 3. 启动服务并设置开机自启
ansible web -b -m ansible.builtin.service -a "name=nginx state=started enabled=yes"

逐台验证返回 200:

ansible web -m ansible.builtin.uri -a "url=http://127.0.0.1 status_code=200"
web1.example.com | SUCCESS => {
"status": 200,
...
}

这三条命令各自幂等,任意一条重复执行都不会把事情搞砸。但注意这已经是「多步骤部署」了——按开头的经验法则,下次再部署就该把这三步写进 playbook,而不是翻 shell 历史。

常见坑​

  • 引号转义地狱:-a 的内容在控制节点 shell 解析一次、到受管节点(用 shell 模块时)再解析一次。ansible web -m shell -a "echo $HOSTNAME" 里的 $HOSTNAME 会被本地 shell 先展开成控制节点的主机名,要用单引号或转义。嵌套超过两层引号时,停手,写 playbook。
  • command 管道的迷惑报错:ansible web -m command -a "cat /etc/passwd | grep root" 不报「不支持管道」,而是把 | 当成字面文件名传给 cat,报 No such file or directory,第一次见很容易懵。记住:要管道就换 -m ansible.builtin.shell。
  • -b 之后权限不足的两种表现:报 Missing sudo password 或 sudo: a password is required,是 sudo 要密码而你没加 -K;报 sudo: unknown user xxx 之类,是 --become-user 指定的用户在受管节点不存在。另外 sudo 后 PATH 会重置(secure_path),自己编译安装的工具提权后找不到,命令写绝对路径最稳。

小结​

  • Ad-hoc 语法一句话:ansible <pattern> -m <模块> -a "参数",提权加 -b,sudo 要密码再加 -K。
  • 模块优先于裸命令:能说出名字的活(装包、拷文件、管服务、建用户)都有幂等的专用模块,command/shell 留给出不了名的活,并用 creates/removes 补救幂等性。
  • 调试三件套:ansible.builtin.ping 验连通、ansible.builtin.setup 看 facts、ansible.builtin.debug 查变量。
  • 一条命令是效率,三条以上是隐患——步骤一多就落进 playbook,这是下一篇的主题。